package net.towee.client.xmpp;

import java.util.Date;

import com.calclab.emite.core.client.bosh.StreamSettings;
import com.calclab.emite.core.client.conn.ConnectionStateChangedEvent;
import com.calclab.emite.core.client.conn.ConnectionStateChangedEvent.ConnectionState;
import com.calclab.emite.core.client.conn.ConnectionStateChangedHandler;
import com.calclab.emite.core.client.conn.StanzaEvent;
import com.calclab.emite.core.client.conn.StanzaHandler;
import com.calclab.emite.core.client.conn.XmppConnection;
import com.calclab.emite.core.client.events.BeforeStanzaSendEvent;
import com.calclab.emite.core.client.events.IQReceivedEvent;
import com.calclab.emite.core.client.events.MessageReceivedEvent;
import com.calclab.emite.core.client.events.PresenceReceivedEvent;
import com.calclab.emite.core.client.packet.IPacket;
import com.calclab.emite.core.client.xmpp.resource.ResourceBindResultEvent;
import com.calclab.emite.core.client.xmpp.resource.ResourceBindResultHandler;
import com.calclab.emite.core.client.xmpp.resource.ResourceBindingManager;
import com.calclab.emite.core.client.xmpp.session.Credentials;
import com.calclab.emite.core.client.xmpp.session.IMSessionManager;
import com.calclab.emite.core.client.xmpp.session.IQManager;
import com.calclab.emite.core.client.xmpp.session.IQResponseHandler;
import com.calclab.emite.core.client.xmpp.session.SessionRequestResultEvent;
import com.calclab.emite.core.client.xmpp.session.SessionRequestResultHandler;
import com.calclab.emite.core.client.xmpp.session.SessionStates;
import com.calclab.emite.core.client.xmpp.session.XmppSessionBoilerPlate;
import com.calclab.emite.core.client.xmpp.stanzas.IQ;
import com.calclab.emite.core.client.xmpp.stanzas.Message;
import com.calclab.emite.core.client.xmpp.stanzas.Presence;
import com.calclab.emite.core.client.xmpp.stanzas.XmppURI;
import com.google.gwt.core.client.GWT;
import com.google.inject.Inject;
import com.google.inject.Singleton;

@Singleton
public class ToweeXmppSession extends XmppSessionBoilerPlate  {
	private final XmppConnection connection;
	private final IQManager iqManager;
	private final IMSessionManager sessionManager;
	private final ResourceBindingManager bindingManager;
	
	private XmppURI userUri;

	@Inject
	public ToweeXmppSession(XmppConnection connection, ResourceBindingManager bindingManager,
			IMSessionManager sessionManager) {
		super(connection.getEventBus());

		this.connection = connection;
		this.iqManager = new IQManager();
		this.sessionManager = sessionManager;
		this.bindingManager = bindingManager;
		
		addHandlers();
	}
	
	/**
	 * Add connection handlers
	 */
	private void addHandlers() {
		connection.addStanzaReceivedHandler(new StanzaHandler() {
			@Override
			public void onStanza(final StanzaEvent event) {
				final IPacket stanza = event.getStanza();
				final String name = stanza.getName();
				
				if (name.equals("message")) {
					eventBus.fireEvent(new MessageReceivedEvent(new Message(stanza)));
				} else if (name.equals("presence")) {
					eventBus.fireEvent(new PresenceReceivedEvent(new Presence(stanza)));
				} else if (name.equals("iq")) {
					final String type = stanza.getAttribute("type");
					
					if ("get".equals(type) || "set".equals(type)) {
						eventBus.fireEvent(new IQReceivedEvent(new IQ(stanza)));
					}
				}
			}
		});

		connection.addConnectionStateChangedHandler(new ConnectionStateChangedHandler() {
			@Override
			public void onStateChanged(final ConnectionStateChangedEvent event) {
				if (event.is(ConnectionState.error)) {
					GWT.log("Connection error: " + event.getDescription());
					setSessionState(SessionStates.error);
				} else if (event.is(ConnectionState.disconnected)) {
					setSessionState(SessionStates.disconnected);
				}
			}
		});

		// Do not use manager, in order to be able to mock on testing
		ResourceBindResultEvent.bind(eventBus, new ResourceBindResultHandler() {
			@Override
			public void onBinded(final ResourceBindResultEvent event) {
				setSessionState(SessionStates.binded);
				sessionManager.requestSession(event.getXmppUri());
			}
		});
		
		// Do not use manager, in order to be able to mock on testing
		SessionRequestResultEvent.bind(eventBus, new SessionRequestResultHandler() {
			@Override
			public void onSessionRequestResult(final SessionRequestResultEvent event) {
				if (event.isSucceed()) {
					userUri = event.getXmppUri();
				}
			}
		});
	}

	@Override
	public XmppURI getCurrentUserURI() {
		return userUri;
	}

	@Override
	public boolean isReady() {
		return userUri != null;
	}
	
	@Override
	public void send(IPacket stanza) {
		// Override from attribute
		stanza.setAttribute("from", userUri.toString());
		stanza.setAttribute("xmlns", "jabber:client");
		
		// Fire event for processing stanza before sending
		eventBus.fireEvent(new BeforeStanzaSendEvent(stanza));
		
		// Send stanza
		connection.send(stanza);
	}

	@Override
	public void sendIQ(String category, IQ iq, IQResponseHandler iqHandler) {
		// Get id
		String id = iqManager.register(category, iqHandler);
		iq.setAttribute("id", id);
		iq.setAttribute("xmlns", "jabber:client");
		
		// Send IQ package
		send(iq);
	}

	@Override
	public void login(Credentials credentials) {
		// Set session as authorized
		setSessionState(SessionStates.authorized);
		
		// Start xmpp session		
		bindingManager.bindResource("Web_"+new Date());
	}

	@Override
	public void logout() {
		if (getSessionState() != SessionStates.disconnected && userUri != null) {
			setSessionState(SessionStates.loggingOut);
			userUri = null;
			
			connection.disconnect();
			setSessionState(SessionStates.disconnected);
		}
	}

	/*
	 * Not needed methods
	 */
	@Override
	public StreamSettings pause() {
		return null;
	}

	@Override
	public void resume(XmppURI userURI, StreamSettings settings) {
	}
}
